package com.apop.demo.encrypt.util.sm;

import com.apop.demo.encrypt.util.Base64Utils;
import com.apop.demo.encrypt.util.HexUtils;
import com.apop.demo.encrypt.util.sm.cipher.SMKeyPair;
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.jcajce.spec.SM2ParameterSpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.math.ec.ECCurve;

import java.security.*;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

public class FzSM3WithSM2 {
    private static final String ALGORITHM_NAME = "SM3withSM2";

    private static final String ECNAME = "sm2p256v1";

    private static final String ALGORITHM = "EC";

    private static final String DEFAULT_USERID = "1234567812345678";

    private static final int RS_LEN = 32;

    private static final X9ECParameters x9 = ECNamedCurveTable.getByName("sm2p256v1");

    public static byte[] sign(PrivateKey key, byte[] data, byte[] userID) throws Exception {
        if (userID == null) {
            userID = "1234567812345678".getBytes();
        }
        SM2ParameterSpec parameterSpec = new SM2ParameterSpec(userID);
        Signature signer = Signature.getInstance("SM3withSM2", "BC");
        signer.setParameter((AlgorithmParameterSpec) parameterSpec);
        signer.initSign(key, new SecureRandom());
        signer.update(data);
        return signer.sign();
    }

    public static byte[] sign(byte[] key, byte[] data, byte[] userID) throws Exception {
        PrivateKey privateKey = getPrivateKey(key);
        if (privateKey == null) {
            throw new IllegalArgumentException("privatekey parameter is illegal! ");
        }
        return sign(privateKey, data, userID);
    }

    public static byte[] sign(String privateKeyStr, int keyType, byte[] data, byte[] userID) throws Exception {
        byte[] privateKeyBytes;
        if (keyType == 1) {
            privateKeyBytes = HexUtils.hexToByte(privateKeyStr);
        } else {
            if (keyType != 2) {
                throw new IllegalArgumentException("keyType parameter is illegal! ");
            }
            privateKeyBytes = Base64Utils.bouncyDecode(privateKeyStr);
        }
        PrivateKey privateKey = getPrivateKey(privateKeyBytes);
        if (privateKey == null) {
            throw new IllegalArgumentException("privatekey parameter is illegal! ");
        }
        return sign(privateKey, data, userID);
    }

    public static boolean verify(PublicKey key, byte[] src, byte[] signedBytes, byte[] userID) throws Exception {
        if (userID == null) {
            userID = "1234567812345678".getBytes();
        }
        SM2ParameterSpec parameterSpec = new SM2ParameterSpec(userID);
        Signature verifier = null;
        verifier = Signature.getInstance("SM3withSM2", "BC");
        verifier.setParameter((AlgorithmParameterSpec) parameterSpec);
        verifier.initVerify(key);
        verifier.update(src);
        return verifier.verify(signedBytes);
    }

    public static boolean verify(byte[] key, byte[] src, byte[] signedBytes, byte[] userID) throws Exception {
        PublicKey publicKey = getPublicKey(key);
        if (publicKey == null) {
            throw new IllegalArgumentException("publickey parameter is illegal! ");
        }
        return verify(publicKey, src, signedBytes, userID);
    }

    public static boolean verify(String publicKeyStr, int keyType, byte[] src, byte[] signedBytes, byte[] userID)
            throws Exception {
        byte[] publicKeyBytes;
        if (keyType == 1) {
            publicKeyBytes = HexUtils.hexToByte(publicKeyStr);
        } else {
            if (keyType != 2) {
                throw new IllegalArgumentException("keyType parameter is illegal! ");
            }
            publicKeyBytes = Base64Utils.bouncyDecode(publicKeyStr);
        }
        PublicKey publicKey = getPublicKey(publicKeyBytes);
        if (publicKey == null) {
            throw new IllegalArgumentException("privatekey parameter is illegal! ");
        }
        return verify(publicKey, src, signedBytes, userID);
    }

    public static PrivateKey getPrivateKey(byte[] keyEntry) throws Exception {
        BouncyCastleProvider bc = new BouncyCastleProvider();
        KeyFactory keyFact = null;
        keyFact = KeyFactory.getInstance("EC", (Provider) bc);
        return keyFact.generatePrivate(new PKCS8EncodedKeySpec(keyEntry));
    }

    public static PublicKey getPublicKey(byte[] keyEntry) throws Exception {
        KeyFactory keyFact = null;
        BouncyCastleProvider bc = new BouncyCastleProvider();
        keyFact = KeyFactory.getInstance("EC", (Provider) bc);
        return keyFact.generatePublic(new X509EncodedKeySpec(keyEntry));
    }

    public static SMKeyPair generateKeyPair(int keyType) throws Exception {
        KeyPairGenerator kpg = null;
        KeyPair keyPair = null;
        ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
        kpg = KeyPairGenerator.getInstance("EC", (Provider) new BouncyCastleProvider());
        kpg.initialize(sm2Spec);
        kpg.initialize(sm2Spec, new SecureRandom());
        keyPair = kpg.generateKeyPair();
        byte[] pubKey = keyPair.getPublic().getEncoded();
        byte[] priKey = keyPair.getPrivate().getEncoded();
        String pubKeyStr = null;
        String priKeyStr = null;
        if (keyType == 1) {
            pubKeyStr = HexUtils.byteToHex(pubKey).toUpperCase();
            priKeyStr = HexUtils.byteToHex(priKey).toUpperCase();
        } else {
            if (keyType != 2) {
                throw new IllegalArgumentException("keyType parameter is illegal! ");
            }
            pubKeyStr = new String(Base64Utils.bouncyEncode(pubKey));
            priKeyStr = new String(Base64Utils.bouncyEncode(priKey));
        }
        SMKeyPair smKeyPair = new SMKeyPair(priKeyStr, pubKeyStr);
        return smKeyPair;
    }

    private static final ECCurve curve = x9.getCurve();

    private static final ECParameterSpec spec = new ECParameterSpec(curve, x9.getG(), x9.getN(), x9.getH());

    static {
        if (Security.getProvider("BC") == null) {
            Security.addProvider((Provider) new BouncyCastleProvider());
        }
    }
}
